<template>
  <div style="height: 100vh; display: flex">
    <div style="width: 200px">
      <div
        draggable="true"
        @dragstart="handleDragStart($event, item)"
        v-for="item in presets"
        class="preset-item">
        {{ item.label }}
      </div>
    </div>
    <div style="width: 800px">
      <GridLayoutContext :plugins="[useDragToLayout]" v-model:list="list"></GridLayoutContext>
    </div>
  </div>
</template>
<script setup lang="ts">
import { cloneDeep } from 'fok-js';
import { FFormItem, FInput } from 'fok-ui';
import { GridLayout, GridItem } from 'grid-layout-plus/dist/grid-layout-plus.cjs';
import {
  defineComponent,
  h,
  inject,
  PropType,
  ref,
  watch,
  InjectionKey,
  provide,
  Ref,
  VNode,
  onMounted,
  nextTick
} from 'vue';

function handleDragStart(event: DragEvent, item) {
  event.dataTransfer?.setData('datagriditem', JSON.stringify(item));
}
type GridContext = {
  updateParentLayout: (changeItem: GridItem) => void;
  getLayoutMap: () => Record<string, GridItem>;
  refresh: () => void;
  renderGridItem: (item: GridItem) => VNode;
  layoutMounted: (
    layoutRef: InstanceType<typeof GridLayout>,
    element: HTMLDivElement,
    gropuId?: string
  ) => void;
};
const gridChildContextKey: InjectionKey<GridContext> = Symbol('grid-layout-child-context');
const LayoutGroup = defineComponent({
  props: {
    groupId: String,
    list: { type: Array as PropType<GridItem[]>, default: () => [] }
  },
  setup: (props) => {
    const gridContext: GridContext = inject(gridChildContextKey)!;
    const wrapper = ref<InstanceType<typeof GridLayout>>();
    const element = ref<HTMLDivElement>();
    return () =>
      h(
        'div',
        { class: 'layout-grid-wrapper', ref: element },
        h(
          GridLayout,
          {
            layout: props.list,
            rowHeight: 60,
            colNum: 24,
            margin: [0, 0],
            ref: wrapper,
            onLayoutMounted: () => {
              setTimeout(() => {
                gridContext.layoutMounted(wrapper.value, element.value!, props.groupId);
              });
            }
          },
          {
            default: () => {
              return props.list.map((item) => {
                let layoutItem;
                if (item.columns) {
                  layoutItem = h(LayoutGroup, {
                    list: item.columns,
                    groupId: item.i
                  });
                } else {
                  layoutItem = gridContext.renderGridItem(item);
                }

                return h(
                  GridItem,
                  {
                    key: item.i,
                    x: item.x,
                    y: item.y,
                    w: item.w,
                    h: item.h,
                    i: item.i,
                    id: 'key-' + item.i,
                    onResized: () => {
                      gridContext.updateParentLayout(item);
                    },
                    onMoved: () => {
                      gridContext.updateParentLayout(item);
                    }
                  },
                  () => layoutItem
                );
              });
            }
          }
        )
      );
  }
});
type GridContextPlugin = () => (options: {
  layoutRef: {
    state: {
      originalLayout: GridItem[];
    };
    dragEvent: (event: 'dragstart' | 'dragend', id: string, ...args: number[]) => void;
  };
  container: HTMLDivElement;
  element: HTMLDivElement;
  groupId?: string;
  refresh: () => void;
  getElement: (groupId?: string) => HTMLDivElement;
  updateParentLayout: (changeItem: GridItem) => void;
  getLayoutMap: () => Record<string, GridItem>;
  getLayout: () => GridItem[];
  updateLayout: (list: GridItem[]) => void;
}) => void;
// 将布局中的操作统一封装
const GridLayoutContext = defineComponent({
  props: {
    list: { type: Array as PropType<GridItem[]>, default: () => [] },
    plugins: { type: Array as PropType<Function[]>, default: () => [] }
  },
  emits: ['update:list', 'change'],
  setup: (props, { emit, expose }) => {
    const vars = {
      layoutMap: {},
      elementMap: {}
    };

    // 构建上下级关联,方便查询
    function initLayoutMap(list: GridItem[]) {
      const layoutMap: Record<string, GridItem> = {};
      function each(list?: GridItem[], parent?: GridItem) {
        list?.forEach((item) => {
          if (parent) item.pi = parent.i;
          layoutMap[item.i] = item;
          if (item.columns) {
            each(item.columns, item);
          }
        });
      }
      each(list);
      vars.layoutMap = layoutMap;
    }

    initLayoutMap(props.list);
    function notify(list) {
      emit('change', list);
      emit('update:list', list);
    }
    const contextMethods = {
      updateParentLayout: (changeItem: GridItem) => {
        const layoutMap = vars.layoutMap;
        let parent = layoutMap[changeItem.pi!];
        while (parent) {
          const maxItem = parent.columns
            .slice()
            .sort((a, b) => a.y - b.y)
            .pop()!;
          // 向上更新父级容器高度
          parent.h = maxItem.y + maxItem?.h;
          parent = layoutMap[parent.pi!];
        }
        contextMethods.refresh();
      },
      getLayoutMap: () => vars.layoutMap,
      refresh: () => {
        const list = [...props.list!];
        notify(list);
      },
      renderGridItem(item) {
        return h('div', { style: 'padding:12px' }, item.label);
      },
      layoutMounted(layout, ele, gid) {
        vars.elementMap[gid!] = ele;
        plugins.forEach((plugin) => {
          plugin({
            layoutRef: layout,
            element: ele,
            groupId: gid,
            refresh: contextMethods.refresh,
            updateParentLayout: contextMethods.updateParentLayout,
            getLayoutMap: () => vars.layoutMap,
            getLayout: () => props.list,
            getElement: (id?: string) => {
              if (id) {
                return vars.elementMap[id];
              }
              return container.value;
            },
            container: container.value,
            updateLayout: (list: GridItem[]) => {
              notify(list);
              nextTick(() => {
                initLayoutMap(list);
              });
            }
          });
        });
      }
    };
    provide(gridChildContextKey, contextMethods);
    // 外部对数组增加删除重构
    expose({
      refresh: (list: GridItem[]) => {
        initLayoutMap(list);
      }
    });
    const plugins = props.plugins.map((plugin) => plugin());
    const container = ref<HTMLDivElement>();
    return () => h('div', { ref: container }, h(LayoutGroup, { list: props.list }));
  }
});

function useGridHistory(items: Ref<GridItem[]>) {
  const cachedList: GridItem[][] = [];
  let peek = 0;
  let needPush = true;
  watch(
    () => items.value,
    (e) => {
      if (!needPush) {
        needPush = true;
      }
      // 如果按了撤销后,再次修改.则删除后面的历史记录
      if (peek < cachedList.length) {
        cachedList.length = peek;
      }
      cachedList.push(cloneDeep(e));
      peek = cachedList.length;
    }
  );
  function undo() {
    if (peek > 0) {
      peek -= 1;
      items.value = cachedList[peek];
      needPush = false;
    }
  }
  function redo() {
    if (peek < cachedList.length - 1) {
      peek += 1;
      items.value = cachedList[peek];
      needPush = false;
    }
  }
  function clear() {
    cachedList.length = 0;
    peek = 0;
    needPush = true;
  }
  return { redo, undo, clear };
}
function calculateTreeHeight(tree: GridItem[]): void {
  const nodes: GridItem[] = []; // 存储从叶子到根的节点顺序

  // Step 1: 使用 BFS 翻转树结构，按从叶子到根的顺序存储节点
  const queue: GridItem[] = tree.slice();
  while (queue.length > 0) {
    const node = queue.shift()!;
    nodes.push(node); // 将当前节点加入结果列表
    if (node.columns && node.columns.length > 0) {
      queue.push(...node.columns); // 将子节点加入队列
    }
  }

  // Step 2: 自底向上计算每个节点的高度
  for (let i = nodes.length - 1; i >= 0; i--) {
    const node = nodes[i];
    if (node.columns && node.columns.length > 0) {
      // 父节点的高度 = 子节点的 y + h 的最大值
      node.h = Math.max(...node.columns.map((child) => child.y + child.h));
    }
  }
}
const presets = ref([
  { label: '输入框', type: 'f-input', config: {} },
  { label: '日期', type: 'f-date', config: {} },
  { label: '下拉', type: 'f-select', config: {} },
  { label: '多行', type: 'f-input-textArea', config: { w: 24 } },
  { label: '空行', type: 'div', config: { w: 24 } }
]);
const useDragToLayout: GridContextPlugin =
  () =>
  ({
    layoutRef,
    element,
    groupId,
    getLayout,
    getLayoutMap,
    updateLayout,
    updateParentLayout,
    getElement
  }) => {
    let width = 0;
    let rect: DOMRect;
    let x = 0;
    let y = 0;
    let isDroped = false;
    // 临时的id,拖拽结束后,如果被放置,需要重置id
    const itemId = 'drop-item';
    // 获取groupId所在的路径,用于添加新的元素
    function getParentPath(id: string) {
      let current: GridItem | null = getLayoutMap()[id];
      const path: (string | number)[] = [];
      while (current) {
        if (current.pi) {
          let parent = getLayoutMap()[current.pi];
          path.push(
            'columns',
            parent.columns.findIndex((c) => c.i === current!.i)
          );
          current = parent;
        } else {
          path.push(
            'columns',
            getLayout().findIndex((item) => item.i == current!.i)
          );
          current = null;
        }
      }
      return path.reverse();
    }
    function hidePlaceholder(id: string) {
      const item = getElement(id).querySelector('.vgl-layout>section:last-child');
      console.log(id, 'placeholder item:', getElement(id), item);
      // item?.setAttribute('style', 'display:none');
    }
    element.addEventListener('dragover', (e) => {
      e.preventDefault();
      e.stopPropagation();
      if (!rect) {
        rect = element.getBoundingClientRect();
        width = rect.width / 24;
      }

      if (!rect?.width) return;

      let addGridItem = getLayoutMap()[itemId];
      // 如果已存在,但是不在当前容器,则删除
      if (addGridItem && addGridItem.pi !== groupId) {
        hidePlaceholder(addGridItem.pi!);
        // 结束拖拽
        layoutRef.dragEvent(
          'dragend',
          itemId,
          addGridItem.x,
          addGridItem.y,
          addGridItem.h,
          addGridItem.w
        );

        remove(addGridItem.pi);
        return;
      }
      // 不存在,则添加到当前容器
      if (!addGridItem) {
        // 添加一个占位的
        drop('{}');
        return;
      }
      if (addGridItem.pi !== groupId) return;

      x = Math.floor((e.clientX - rect.left) / width);
      y = Math.floor((e.clientY - rect.top) / 60);
      layoutRef.dragEvent('dragstart', itemId, x, y, addGridItem.h, addGridItem.w);
    });
    element.addEventListener('drop', (e) => {
      e.preventDefault();
      e.stopPropagation();
      let addGridItem = getLayoutMap()[itemId];
      const data = e.dataTransfer?.getData('datagriditem');

      if (addGridItem && data) {
        const dataConfig = JSON.parse(data);
        layoutRef.dragEvent('dragend', itemId, x, y, 1, 6);
        isDroped = true;

        const path = getParentPath(addGridItem.pi!);
        let updateItem;
        // 对ID进行更新
        update(path, (list) => {
          return list.map((item) => {
            if (item.i === itemId) {
              const key = 'new-' + Date.now().toString();
              return (updateItem = {
                ...item,
                label: dataConfig.label,
                type: dataConfig.type,
                i: key,
                field: key,
                ...(dataConfig.config || {}),
                x,
                y
              });
            }
            return item;
          });
        });

        setTimeout(() => {
          updateParentLayout(updateItem);
          updateItem = null;
        });
      }
    });
    element.addEventListener('dragleave', (e) => {
      e.stopPropagation();
      e.preventDefault();

      let dropedItem = getLayoutMap()[itemId];
      // 不是同一个容器,不处理
      if (dropedItem?.pi !== groupId) return;

      // 确保只触发一次事件
      if (element.contains(e.relatedTarget as Node)) {
        return;
      }
      if (dropedItem) layoutRef.dragEvent('dragend', itemId, x, y, dropedItem.h, dropedItem.w);
      if (!isDroped && dropedItem) {
        remove(dropedItem.pi);
      }
    });

    function dropGridItem(item: any, layout = getLayout()) {
      let grid = JSON.parse(item);
      let layoutList: GridItem[] = [];
      const gridItem = {
        x: 0,
        y: 0,
        w: 6,
        h: 1,
        i: itemId,
        field: itemId,
        pi: groupId,
        type: grid.type,
        label: grid.label
      };

      if (groupId) {
        layoutList = immutableEdit(layout, getParentPath(groupId), (list) => {
          return list.concat(gridItem);
        });
      } else {
        layoutList = layout.concat(gridItem as any);
      }

      return layoutList;
    }
    /** 移除已经添加的数据 */
    function removeDropedGrid(groupId: string, layout = getLayout()) {
      let dropedItem = getLayoutMap()[itemId];
      if (!dropedItem) return;

      let layoutList;
      if (groupId) {
        layoutList = immutableEdit(layout, getParentPath(groupId), (list) => {
          return list.filter((item) => {
            let flag = item.i !== itemId;
            if (!flag) {
              console.log(item.i, 'remove from :', groupId);
            }
            return flag;
          });
        });
      } else {
        layoutList = layout.filter((item) => {
          let flag = item.i !== itemId;
          if (!flag) {
            console.log(item.i, 'remove from :', groupId);
          }
          return flag;
        });
      }
      return layoutList;
    }

    function update(path, callback: (items: GridItem[]) => GridItem[]) {
      updateLayout(immutableEdit(getLayout(), path, callback));
    }

    function drop(e: any) {
      const list = dropGridItem(e);
      if (list) {
        updateLayout(list);
      }
    }
    function remove(groupId) {
      const list = removeDropedGrid(groupId);
      if (list) {
        updateLayout(list);
      }
    }
  };

const list = ref(
  initGridLayout([
    { label: '脐静脉置管长度', field: 'new001114' },
    { label: 'PICC置管第几天', field: 'new001117' },
    { label: '拔管时间', field: 'new001136' },
    { label: '拔管原因', field: 'new001137' },
    { label: 'PICC置管长度', field: 'new001118' },

    {
      label: '其他',
      field: 'group-1',
      columns: [
        { label: '脐静脉置管长度', field: 'new0011141' },
        { label: 'PICC置管第几天', field: 'new0011171' },
        { label: '拔管时间', field: 'new0011361' },
        { label: '拔管原因', field: 'new0011371' },
        { label: 'PICC置管长度', field: 'new0011181' },

        {
          label: 'PICC置管日期',
          field: 'group-2',
          columns: [
            { label: '脐静脉置管长度', field: 'new00111412' },
            { label: 'PICC置管第几天', field: 'new00111712' },
            { label: '拔管时间', field: 'new00113612' },
            { label: '拔管原因', field: 'new00113712' },
            { label: 'PICC置管长度', field: 'new00111812' }
          ]
        }
      ]
    }
  ] as GridItem[])
);

type GridItem = {
  x: number;
  y: number;
  w: number;
  h: number;
  i: string;
  pi?: string;
  label: string;
  field: string;
  columns: GridItem[];
};
// 按照网格,一次排放
function initGridLayout(list: GridItem[], initSpan = 6) {
  const initItems = (list) => {
    let x = 0;
    let y = 0;
    return list.map((item) => {
      item.x = x;
      item.y = y;
      item.w = initSpan;
      item.h = 1;
      item.i = item.field;
      x += item.w;
      if (x % 24 === 0) {
        y += 1;
        x = 0;
      }
      // 默认对子级进行沾满行处理
      if (item.columns?.length) {
        // 另起一行
        if (x > 0) y += 1;
        item.y = y;
        item.x = 0;
        item.w = 24;
        item.isResizable = false; // 嵌套容器禁止缩放
        item.columns = initItems(item.columns);
        item.h = Math.ceil(item.columns.length / (24 / initSpan));
      }
      return item;
    });
  };
  const layoutList = initItems(list);
  // 重新计算高度,父级高度= 子级Max(y + h)
  calculateTreeHeight(layoutList);
  return layoutList;
}

type Path = Array<string | number>;
type EditCallback<T> = (currentValue: T) => T;

function immutableEdit<T>(data: T, path: Path, callback: EditCallback<any>): T {
  // 基础递归终止条件
  if (path.length === 0) return callback(data);

  const [currentKey, ...remainingPath] = path;

  // 处理数组类型
  if (Array.isArray(data)) {
    const index = currentKey as number;
    if (isNaN(index)) {
      throw new Error(`Array path must use numbers`);
    }
    if (index < 0 || index >= data.length) {
      throw new Error(`Array index ${currentKey} out of bounds`);
    }
    return [
      ...data.slice(0, index),
      immutableEdit(data[index], remainingPath, callback),
      ...data.slice(index + 1)
    ] as T;
  }

  // 处理对象类型
  if (typeof data === 'object' && data !== null) {
    const key = currentKey as string;
    if (!(key in data)) {
      throw new Error(`Object path must use valid keys`);
    }
    return {
      ...data,
      [key]: immutableEdit((data as any)[key], remainingPath, callback)
    };
  }

  // 到达路径终点前遇到原始值（路径无效）
  throw new Error(`Invalid path segment: ${currentKey}`);
}
</script>

<style scoped>
.vgl-layout::before {
  position: absolute;
  width: calc(100% - 10px);
  height: calc(100% - 10px);
  margin: 0px;
  content: '';
  background-image: linear-gradient(to right, #f8f8f8 1px, transparent 1px),
    linear-gradient(to bottom, #f8f8f8 1px, transparent 1px);
  background-repeat: repeat;
  background-size: calc(100% / 24) 60px;
}
.vgl-layout {
  border: 1px solid #ececec;
}
</style>
